import socket
import sys
import threading
import time
import select
import BufferFile

RECV_BUFF_SIZE=1500
SEND_BUFF_SIZE=1500
DATA_BUFF_SIZE=256*1024

MAX_TIMEOUT=30

class SimpWSConnection:
    """links a socket with a handler
    
    also usable by select.select to test for 
    read-ready status"""
    
    def __init__(self,soc,handler_class):
        
        self.soc=soc
        self.soc.setblocking(0)
        self.soc.setsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF,SEND_BUFF_SIZE*2)
        self.soc.setsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF,RECV_BUFF_SIZE*4)
        self.buffer=BufferFile.BufferFile(DATA_BUFF_SIZE)
        self.handler=handler_class(self)
        self.is_closed=False
        self.data_sent=0
        self.data_count=0
        self.lasttime=time.clock()

                    
    def readstream(self):
        """ reads and handles data from the stream """
        
        try:
            data=self.soc.recv(RECV_BUFF_SIZE)
            
            if(len(data)==0):#err ... connection closed
                self.is_closed=True
                print("Remote host has closed connection!")
                return

            print("Recieving:\n"+data+"\nAt "+str(len(data))+" bytes")
            self.handler.got_data(data)
            self.lasttime=time.clock()
            
            
        except socket.error:
            print("WARNING: socket error, closing connection")
            self.is_closed=True
        
        
    def writestream(self):
        """send waiting data to the stream"""
        
        try:
            self.buffer.sendtosock(self.soc,SEND_BUFF_SIZE)
            self.lasttime=time.clock()
            
        except socket.error:
            print("WARNING: socket error, closing connection")
            self.is_closed=True
            
        
    def data_waiting(self):
        """ return the number of bytes left in the buffer"""
    
        return len(self.buffer.data)>0
            

    def close(self):
        """close the connection"""
        
        self.is_closed=True #the server thread will pick this up on next pass
        
        
    def fileno(self):
        """so this can be read by select"""
        return self.soc.fileno()


        
class SimpWSServer:
    """allows for multiple client connections 
    
    accepts a tuple of tuples containing (hostname,port)
    of each socket that will be used by this server 
    to listen for incomming connections"""

    def __init__(self,address_list,handler_class):
        self.num_connections=0
        self.listeners=[]
        self.connections=[]
        self.handlers=[]
        self.handler_class=handler_class
        
        for a in address_list:
            self.listeners.append(threading.Thread(target=self._listen_loop,args=(a,)))

        self.conn_thread=threading.Thread(target=self._conn_loop)
        self.fileio_thread=threading.Thread(target=self._fileio_loop)
        self.exit=False
        

    def server_run(self):
        """start the server loop threads"""

        for l in self.listeners:
            l.start()
            
        self.conn_thread.start()
        self.fileio_thread.start()
        
        
    def server_stop(self):
        """cleanup stuff and exit threads"""
        
        self.exit=True
        
        
    def _fileio_loop(self):
        """tells handlers to execute file io
        
        will call file io on handlers that have io
        waiting for each 100 ms; also checks for timeouts"""
        
        while self.exit==False:
        
            try:
                t=time.clock()
                for h in self.handlers:
                    h.do_file_io()
                        
                for c in self.connections:
                    if c.lasttime+MAX_TIMEOUT<t:
                        c.is_closed=True
                        print("connection time out'd")
                        
            except Exception as e:
                print("WARNING: Unhandled Exception in file io thread.")
                print(e)
                    
            time.sleep(0.1)    
            
	
    def _listen_loop(self,address):
        """listen for incomming connections at address"""

        listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listener.bind(address)
        listener.listen(5)
        print("listening at %s on port %s\n"% listener.getsockname())
        
        while self.exit==False:
        
            try:
                (soc,pho)=listener.accept()
                self.num_connections+=1
                print("got a connection!\ntotal connections=%s" 
                    %(self.num_connections,))
                new_con=SimpWSConnection(soc,self.handler_class)    
                self.connections.append(new_con)
                self.handlers.append(new_con.handler)
                
            except Exception as e:
                print("WARNING: Unhandled exception in listener loop.")
                print(e)
                
    def _conn_loop(self):
        """recieve and handle incomming data
        
        also remove time-out'd connections"""
        
        read_ready = [];
        write_ready=[];
        err_ready=[];
        
        while self.exit==False:
            
            try:
                if(len(self.connections)>0): #wait for the first connection
                
                    try:
                        read_ready,write_ready,err_ready=select.select(self.connections,self.connections,self.connections ,0)
                        
                    except select.error:
                        print("WARNING: select error, attempting to remove bad connection")
                        self._handle_select_error()
                        
                    else:
                        
                        for c in err_ready:
                            self._remove_connection(c)
                    
                        for c in read_ready:
                            c.readstream()
                            
                        for c in write_ready:
                            if c.data_waiting():
                                c.writestream()
                            
                    for c in self.connections: 
                        if c.is_closed:
                            self._remove_connection(c)
                
            except Exception as e:
                    print("WARNING: unhandled exception in connection loop.")
                    print (e)
            
            
    def _remove_connection(self,c):
        """removes a connection and its handler"""
        
        c.is_closed=True
        self.connections.remove(c)
        self.handlers.remove(c.handler)
        c.handler.handle_disconnect()
        c.soc.close()
        print("closing connection")
        
        
    def _handle_select_error(self):
        """handles errors with select calls"""
        
        for c in self.connections:
        
            a=[c]
            
            try:
                read_ready,write_ready,err_ready=select.select(a,a,a,0)
                
            except:
                self._remove_connection(c)